界面设计的各种选择

  界面的作用就是尽可能减小程序不同部分之间的相互依赖。最小的界面将会使程序易于理解,有很好的数据隐蔽性质,容易修改,也编译得更快。

  在考虑依赖性的时候,一件很重要的事就是记住编译器和程序员都倾向于取一种对他们最朴素的认识:“如果一个定义在位置X处于作用域之中,那么在位置X写出的所有东西都将依赖于这个定义所说的所有东西”。在典型情况下,问题实际上不会那么糟,因为大部分定义与大部分代码无关。看看我们前面使用的定义,考虑

    namespace Parser {
        // ...
        double expr(bool);
        // ...
    }

    int main()
    {
        // ...
        Parser::expr(false);
        // ...
    }

函数main()只依赖于Parser::expr(),但这需要耗费时间、脑力、计算等去弄清楚。因此,对于真实规模的程序,人和编译系统常常会按照最安全的方式去做,在那些可能有依赖的地方,就假定它确实有。通常这也是完全合理的方式。

  在这种情况下,我们的目标就应该是去描述自己的程序,将这种具有潜在依赖性的集合缩小到实际依赖性的集合。

  我们首先试最明显的东西,用我们已有的实现界面为Parser定义一个用户界面:

    namespace Parser {            // 给实现的界面
        // ...
        double expr(bool);
        // ...
    }

    namespace Parser_interface {  // 给用户的界面
        using Parser::expr;
    }

事情很清楚,Parser_interface的用户仅仅依赖于Parser::expr(),而且是间接的。然而,对于依赖图的粗略观察给我们的是:

现在,看起来Driver还是很容易受到Parser界面修改的伤害,而原本是希望它能与之隔离。甚至依赖关系的这种外表形式也是我们所不希望的,所以我们应显式地限制Parser_interface对Parser的依赖,使得在定义Parser_interface的位置,只有分析器实现界面中有关的部分(它以前被称做Parser`)在作用域中

    namespace Parser {              // 给用户的界面
        double expr(bool);
    }

    namespace Parser_interface {    // 给用户的独立命名的界面
        using Parser::expr;
    }

或者用图形

为了保证Parser和Parser`的一致性,我们还是需要依赖于编译系统的整体,而不能只靠在单独的编译单位上工作的编译器。这种解法与8.2.4节的不同之处只是另有了一个名字空间Parser_interface。如果我们希望的话,也可以给Parser_interface一个自己的expr()函数:

    namespace Parser_interface {
        double expr(bool);
    }

现在再去定义Parser_interface时,Parser已不必在作用域里了,只有在定义Parser_interface::expr()的时候它才需要在作用域里:

    double Parser_interface::expr(bool get)
    {
        return Parser::expr(get);
    }

最后的这一版本可以图示为:

现在所有的依赖性都已经最小化。每个东西都是具体的和适当命名的。当然,对于我所面对的大部分问题而言,这一解决方案也太过分了。

🔚